iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
3

瞭解了基本的粒子系統如何建立後,今天要來練習實作粒子系統中常見的應用:雪花。以下將會透過建立粒子系統、載入貼圖、雪花飄落動畫來一步步做出華麗的粒子特效。

14_front

Photo by Aaron Burden on Unsplash


這是本系列第 14 篇,如果還沒看過第 13 篇可以點以下連結前往:

用 Three.js 來當個創世神 (13):粒子系統初探


雪花粒子系統

按照慣例,今天的成果圖與 demo,下面就直接開始實作吧:

14_2

請參考完整原始碼成果展示

 

實作

1. 建立粒子系統

// points
const particleCount = 15000
let points

// 建立粒子系統
function createPoints() {
  const geometry = new THREE.Geometry()

  const material = new THREE.PointsMaterial({
    size: 3
  })

  const range = 250
  for (let i = 0; i < particleCount; i++) {
    const x = THREE.Math.randInt(-range, range)
    const y = THREE.Math.randInt(-range, range)
    const z = THREE.Math.randInt(-range, range)

    const point = new THREE.Vector3(x, y, z)
    geometry.vertices.push(point)
  }

  points = new THREE.Points(geometry, material)
  scene.add(points)
}

首先一開始先建立粒子系統,這裡我們建立 15000 個頂點,並且將每一個頂點的(x, y, z)值分別設定在介於(-250, 250)的隨機值,並利用自訂的頂點群建立粒子系統,將相機拉遠後看到的成果會是一個類似邊長為 500 的正立方體粒子群。

14_1

THREE.Math.randInt(min, max) 是 Three.js 中提供一個類似 Math.random() 的包裝,可直接指定最小值與最大值,會算出這區間隨機數。

2. 載入雪花貼圖與材質屬性調整

const texture = new THREE.TextureLoader().load('./snowflake.png')
let material = new THREE.PointsMaterial({
  size: 5,
  map: texture,
  blending: THREE.AdditiveBlending,
  depthTest: false,
  transparent: true,
  opacity: 0.7
})

這邊稍微筆記一下關於 THREE.PointsMaterial 中幾個在粒子材質中需注意的屬性:

屬性 說明
color 粒子系統中所有粒子的材質顏色。若 vertexColors 設為 true,則會將此值乘以頂點顏色得到最終呈現的顏色。預設為 0xffffff 白色。
vertexColors 設為 THREE.VertexColors,則會使用頂點的顏色。預設為 THREE.NoColors
blending 渲染材質時的融合模式。用來調整載入的材質如何與背景融合。
depthTest 深度緩衝。設為 true 時, 粒子會有深度緩衝效果,根據 z 座標決定物體間的遮擋效果,此屬性預設為 true
sizeAttenuation 尺寸衰減。設為 true 時, 粒子的大小會隨著與相機的距離而不同;設為 false 時,則無論粒子距離相機遠近大小皆相同,可見官方範例。此屬性預設為 true

首先是載入雪花貼圖: snowflake

並且將融合模式設為 THREE.AdditiveBlending,就是在渲染粒子時背景的顏色會被添加到粒子的背景上,簡單的說就是可以幫載入的貼圖去背,如果沒設這個屬性,會看到每個雪花都帶著醜醜的黑色背景。

depthTest 有關掉的話,會將融合模式中吃到的背景色,在兩片雪花疊加時有透明效果而不會被遮擋住。

3. 相機設定及場景霧化

scene.fog = new THREE.FogExp2(0x000000, 0.0008)

// camera
camera = new THREE.PerspectiveCamera(
  70,
  window.innerWidth / window.innerHeight,
  1,
  1000
)
camera.position.set(0, 0, 100)
camera.lookAt(scene.position)

設定場景的霧化效果,可以讓極遠處的粒子有種模糊美,而相機視角及位置稍微拉大拉遠來觀察更多的粒子。

 

今日小結

今天實作了粒子系統中一個靜態的雪花畫面,從建立粒子系統並透過載入貼圖的方法設定粒子材質的屬性,讓場景中出現滿滿的雪花,明天會繼續將雪花飄落的動畫完成,我們明天見!

最後再附上今天的完整原始碼成果展示

 

參考資料


上一篇
用 Three.js 來當個創世神 (13):粒子系統初探
下一篇
用 Three.js 來當個創世神 (15):粒子系統 - 雪花 Part.2
系列文
用 Three.js 來當個創世神31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
挖洗菜呱
iT邦新手 5 級 ‧ 2018-10-30 10:42:44

太強

ckchuang iT邦新手 4 級 ‧ 2018-10-30 11:22:14 檢舉

都是從K.唧唧呱呱兩位 3D 大師那學習的!/images/emoticon/emoticon32.gif

我根本不會啊XDDDDD

1
1
ShawnGood
iT邦新手 5 級 ‧ 2022-03-21 14:10:15

用vscode打開smokeflake.png那張圖,可以發現它是不帶透明度的
這邊給你參考一下:THREE.AdditiveBlending指的不是去背,是加色喔

下面是Three.js的原始碼
我們直接看不是premultipliedAlpha的部分
(premultipliedAlpha指的是:圖片像素存的是color.rgb=color.rgb*color.a)
https://ithelp.ithome.com.tw/upload/images/20220321/20106016rRbDEYz8pT.png

case AdditiveBlending:
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
    // 上面的意思是
    // finalColor = srcColor.rgba*srcColor.a + desColor.rgba*1
    // srcColor、desColor都有rgba
    // srcColor是指跑完pixelshader(它會修改顏色)後的像素顏色
    // destinationColor是指已經在畫布(color buffer)上的像素
    break;

原來畫面的背景是黑的所以
desColor =(0,0,0,1)
在畫雪片時,如果該像素抓到的貼圖是白的(1,1,1,1)
srcColor =(1,1,1,1)

但是你的material有設transparent

let material = new THREE.PointsMaterial({
    size: 5,
    map: texture,
    blending: THREE.NormalBlending,
    depthTest: false,
    transparent: true,
    opacity: 0.7
  })

所以跑完pixelshader傳出的顏色是(0.7,0.7,0.7,1)
就是srcColor =(0.7,0.7,0.7,1)

finalColor = srcColor.rgba * srcColor.a + desColor.rgba * 1
finalColor = (0.7,0.7,0.7,1).rgba * srcColor.a + (0,0,0,1) * 1
finalColor = (0.7,0.7,0.7,1).rgba * 1 + (0,0,0,1) * 1
finalColor = (0.7,0.7,0.7,1).rgba * 1 + (0,0,0,1) * 1

alpha的範圍應該會被限制在0~1,所以
finalColor = (0.7,0.7,0.7,1)

如果該像素抓到的貼圖是黑的(1,1,1,1)
執行完THREE.AdditiveBlending,finalColor就還是黑的
看起來就像去背,但它真的不是去背也
(如果要去背,用的是alphaTest,然後貼圖要帶透明資訊a)

gl.blendFunc和gl.blendFuncSeparate的差別可以參考這裡
http://www.jiazhengblog.com/blog/2017/01/04/2989/

0
rainbowrain
iT邦新手 2 級 ‧ 2022-04-21 11:32:36

最近跟著這篇系列文在認識 three.js,感謝 CK Chuang 的教學。

因為從 r125 版開始 THREE.Geometry() 就被移除了。

分享一下 r139 版的範例

    // 主要元件
    let scene, renderer, camera, material, cameraControl;

    const loader = new THREE.TextureLoader();

    // 粒子系統 變數
    const particleCount = 1500; // 粒子數量

    // 粒子系統 函式
    function createSnows() {
      const snowUrl = "https://i.postimg.cc/d0SHZk3J/snow.png"      
      const particleTexture = loader.load(snowUrl)
      const geometry = new THREE.BufferGeometry();
      const vertices = [];

      const range = 250; // 粒子分佈範圍

      for (let i = 0; i < particleCount; i++) {
        const x = THREE.Math.randInt(-range, range);
        const y = THREE.Math.randInt(-range, range);
        const z = THREE.Math.randInt(-range, range);

        vertices.push(x, y, z);
      }

      geometry.setAttribute(
        'position', 
        new THREE.Float32BufferAttribute(vertices, 3)
      );

      material = new THREE.PointsMaterial({
        size: 5,
        map: particleTexture,
        sizeAttenuation: true,
        alphaTest: 0.5,
        transparent: true,
      })

      const particles = new THREE.Points(geometry, material);

      scene.add(particles);
    }

CodePen 程式碼 範例
CodePen 成果 範例

我要留言

立即登入留言